package server;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.Socket;
import java.net.SocketException;
import java.util.ArrayList;

/**
 * Classe creata quando viene presa una connessione tramite protocollo socket con un client. Questa clase viene eseguita da un thread dedicato, per permettere di restare sempre in ascolto sulla porta sulla quale e' stata instaurata la connessione. La classe si occupa anche di prendere i messaggi ricevuti, spacchettarli, chiamare la logica del server e inviare il messaggio di risposta.
 */
public class Server_listener extends Thread{
	
	private enum Metodo
	{
		creaUtente, login, creaRazza, accessoPartita, uscitaPartita, listaGiocatori, classifica, logout, mappaGenerale, listaDinosauri, vistaLocale, statoDinosauro, muoviDinosauro, cresciDinosauro, deponiUovo, confermaTurno, passaTurno;
	}
	private Server_socket hs;
	private Gioco gioco;
	private Partita partita;
	private Socket sock;
	private BufferedReader buffin;
	private BufferedWriter buffout;
	private int tokenassociato = 0;
	
	/**
	 * Costruttore di classe: riceve i riferimenti agli oggetti necessari alla gestione della partita.
	 * @param gio Ogetto Gioco.
	 * @param part Ogetto Partita.
	 * @param so Oggetto socket indicante il socket sul quale e' stato istanziata la connessione.
	 * @param ss Oggetto Server_socket.
	 */
	public Server_listener(Gioco gio, Partita part, Socket so, Server_socket ss){
		this.partita = part;
		this.gioco = gio;
		this.sock = so;
		this.hs = ss;
	}
	
	/**
	 * Metodo necessario a far partire il thread. Istanzia gli oggetti necessari per rimanere in ascolto sul client e inviargli i messaggi.
	 */
	public void run(){
		try {
			buffin = new BufferedReader(new InputStreamReader(sock.getInputStream()));
			buffout = new BufferedWriter(new OutputStreamWriter(sock.getOutputStream()));
			listening();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * Metodo che permette al thread di rimanere in ascolto sulla porta indicata. Nel caso il messaggio sia corretto, chiama un metodo che permetta di spacchettare il messaggio ricevuto.<br \>
	 * In caso di stringa non valida, viene lanciata un eccezione, per esempio quando il client si disconnette.
	 * @throws IOException Eccezione lanciata quando la stringa ricevuta non e' valida. In tal caso, la connessione viene chiusa, l'utente viene fatto uscire dalla partita e viene chiuso il thread.
	 */
	@SuppressWarnings("deprecation")
	private void listening() throws IOException{
		String stringa = null;
		
		while(true){
			try{
			stringa = buffin.readLine();
			System.out.println("Ricevuto messaggio: (" + stringa + ")");
			if( stringa.length() > 0 )
				spacchetta(stringa);
			else comandoNonValido();
			} catch (NullPointerException e){
				if(tokenassociato != 0){
					partita.uscitaPartita(tokenassociato);
					gioco.logout(tokenassociato);
					hs.closeConnection(this.sock, Thread.currentThread());
					sock.close();
					this.stop();
				}
				else hs.closeConnection(this.sock, Thread.currentThread());
				sock.close();
				this.stop();
			}catch (SocketException e){
				if(tokenassociato != 0){
					partita.uscitaPartita(tokenassociato);
					gioco.logout(tokenassociato);
					hs.closeConnection(this.sock, Thread.currentThread());
					sock.close();
					this.stop();
				}
				else hs.closeConnection(this.sock, Thread.currentThread());
				sock.close();
				this.stop();
			}
		}
	}
	
	/**
	 * Metodo usato in caso il messaggio ricevuto non sia riconosciuto tra i messaggi che si possano ricevere.
	 */
	private void comandoNonValido(){
		sender("@comandoNonValido");
	}
	
	/**
	 * Metodo che spacchetta il messaggio ricevuto e, in base alla prima stringa estrapolata, chiama il metodo relativo sulla logica del server. Sulla risposta da parte della logica, se e' andato a buon fine l'operazione viene creata una stringa corretta da mandare al server contenente le informazioni richieste dal client. In caso di errore, osservando il nuero di errore ricevuto dalla logica del server, viene creata una stringa relativa all'errore creatosi.
	 * @param mess Stringa ricevuta dal client, eliminando la prima parte (la chiocciola).
	 * @throws IOException Lanciata nel caso di eventuali errori sul casting.
	 */
	private void spacchetta(String mess) throws IOException{
		int i = 0;
		int j = 0;
		int k, l;
		int tok = 0;
		String metodo, user, pass, nome, tipo;
		String result = "";
		ArrayList<Object> control = new ArrayList<Object>();
		String[] util;
		String[][] utilmat;
		
		i = mess.indexOf(",");
		metodo = mess.substring(1, i);
		switch(Metodo.valueOf(metodo)){
		
			case creaUtente:
				i = mess.indexOf("=");
				j = mess.indexOf(",", i+1);
				user = mess.substring(i+1, j);
				i = mess.indexOf("=", j+1);
				pass = mess.substring(i+1);
				control = gioco.creaUtente(user, pass);
				if ( (Boolean) control.get(0) == false )
					result = errore(Integer.parseInt(String.valueOf(control.get(1))));
				else{
					result = "@ok";
				}
				sender(result);
				break;
				
			case login:
				i = mess.indexOf("=");
				j = mess.indexOf(",", i+1);
				user = mess.substring(i+1, j);
				i = mess.indexOf("=", j+1);
				pass = mess.substring(i+1);
				control = gioco.login(user, pass);
				if ( (Boolean) control.get(0) == false )
					result = errore(Integer.parseInt(String.valueOf(control.get(1))));
				else{
					result = "@ok," + Integer.parseInt(String.valueOf(control.get(1)));
					this.tokenassociato = Integer.parseInt(String.valueOf(control.get(1)));
				}
				sender(result);
				break;
				
			case creaRazza:
				i = mess.indexOf("=");
				j = mess.indexOf(",", i+1);
				tok = Integer.parseInt(mess.substring(i+1, j));
				i = mess.indexOf("=", j+1);
				j = mess.indexOf(",", i+1);
				nome = mess.substring(i+1, j);
				i = mess.indexOf("=", j+1);
				tipo = mess.substring(i+1);
				control = gioco.creaRazza(tok, nome, tipo);
				if ( (Boolean) control.get(0) == false )
					result = errore(Integer.parseInt(String.valueOf(control.get(1))));
				else{
					result = "@ok";
				}
				sender(result);
				break;
				
			case accessoPartita:
				i = mess.indexOf("=");
				tok = Integer.parseInt(mess.substring(i+1));
				control = partita.accessoPartita(tok);
				if ( (Boolean) control.get(0) == false ){
					result = errore(Integer.parseInt(String.valueOf(control.get(1))));
					sender(result);
				}
				else{
					result = "@ok";
					sender(result);
					if ( (Boolean) control.get(1) == true )
						partita.cambioTurno();
				}
				break;
				
			case uscitaPartita:
				i = mess.indexOf("=");
				tok = Integer.parseInt(mess.substring(i+1));
				control = partita.uscitaPartita(tok);
				if ( (Boolean) control.get(0) == false ){
					result = errore(Integer.parseInt(String.valueOf(control.get(1))));
					sender(result);
				}
				else{
					result = "@ok";
					sender(result);
				}
				break;
				
			case listaGiocatori:
				i = mess.indexOf("=");
				tok = Integer.parseInt(mess.substring(i+1));
				control = partita.listaGiocatori(tok);
				if ( (Boolean) control.get(0) == false )
					result = errore(Integer.parseInt(String.valueOf(control.get(1))));
				else{
					util = (String[]) control.get(1);
					result = "@listaGiocatori";
					for ( k = 0; k < util.length; k++){
						result = result + "," + util[k];
					}
				}
				sender(result);
				break;

			case classifica:
				i = mess.indexOf("=");
				tok = Integer.parseInt(mess.substring(i+1));
				control = gioco.classifica(tok);
				if ( (Boolean) control.get(0) == false )
					result = errore(Integer.parseInt(String.valueOf(control.get(1))));
				else{
					result = "@classifica";
					for( k = 1; k < control.size(); k++ ){
						util = (String[]) control.get(k);
							result = result + ",{" + util[0] + "," + util[1] + "," + util[2] + "," + util[3] + "}";
					}
				}
				sender(result);
				break;
				
			case logout:
				i = mess.indexOf("=");
				tok = Integer.parseInt(mess.substring(i+1));
				control = gioco.logout(tok);
				if ( (Boolean) control.get(0) == false )
					result = errore(Integer.parseInt(String.valueOf(control.get(1))));
				else{
					result = "@ok";
				}
				sender(result);
				break;
				
			case mappaGenerale:
				i = mess.indexOf("=");
				tok = Integer.parseInt(mess.substring(i+1));
				control = partita.mappaGenerale(tok);
				if ( (Boolean) control.get(0) == false )
					result = errore(Integer.parseInt(String.valueOf(control.get(1))));
				else{
					utilmat = (String[][]) control.get(3);
					result = "@mappaGenerale,{" + Integer.parseInt(String.valueOf(control.get(1))) + "," + Integer.parseInt(String.valueOf(control.get(2))) + "},";
					for( k = 0; k < Integer.parseInt(String.valueOf(control.get(1))); k++){
						for ( l = 0; l < Integer.parseInt(String.valueOf(control.get(1))); l++){
							result = result + "[" + utilmat[k][l] + "]";
						}
						result = result + ";";
					}
				}
				sender(result);
				break;
				
			case listaDinosauri:
				i = mess.indexOf("=");
				tok = Integer.parseInt(mess.substring(i+1));
				control = partita.listaDinosauri(tok);
				if ( (Boolean) control.get(0) == false )
					result = errore(Integer.parseInt(String.valueOf(control.get(1))));
				else{
					util = (String[]) control.get(1);
					result = "@listaDinosauri";
					for ( k = 0; k < util.length; k++){
						result = result + "," + util[k];
					}
				}
				sender(result);
				break;
			
			case vistaLocale:
				i = mess.indexOf("=");
				j = mess.indexOf(",", i+1);
				tok = Integer.parseInt(mess.substring(i+1, j));
				i = mess.indexOf("=", j+1);
				pass = mess.substring(i+1);
				control = partita.vistaLocale(tok, pass);
				if ( (Boolean) control.get(0) == false )
					result = errore(Integer.parseInt(String.valueOf(control.get(1))));
				else{
					utilmat = (String[][]) control.get(5);
					result = "@vistaLocale,{" + Integer.parseInt(String.valueOf(control.get(1))) + "," + Integer.parseInt(String.valueOf(control.get(2))) + "}," + "{" + Integer.parseInt(String.valueOf(control.get(3))) + "," + Integer.parseInt(String.valueOf(control.get(4))) + "},";
					for( k = 0; k < Integer.parseInt(String.valueOf(control.get(3))); k++){
						for ( l = 0; l < Integer.parseInt(String.valueOf(control.get(4))); l++){
							result = result + "[" + utilmat[k][l] + "]";
						}
						result = result + ";";
					}
				}
				sender(result);
				break;
				
			case statoDinosauro:
				i = mess.indexOf("=");
				j = mess.indexOf(",", i+1);
				tok = Integer.parseInt(mess.substring(i+1, j));
				i = mess.indexOf("=", j+1);
				pass = mess.substring(i+1);
				control = partita.statoDinosauro(tok, pass);
				if ( (Boolean) control.get(0) == false )
					result = errore(Integer.parseInt(String.valueOf(control.get(1))));
				else{
					result = "@statoDinosauro," + String.valueOf(control.get(1)) + "," + String.valueOf(control.get(2)) + "," + String.valueOf(control.get(3)) + ",{" + String.valueOf(control.get(4)) + "," + String.valueOf(control.get(5)) + "}," + String.valueOf(control.get(6));
					if( control.size() != 7 ){
						result = result + ","  + String.valueOf(control.get(7)) + "," + String.valueOf(control.get(8));
					}
				}
				sender(result);
				break;
				
			case muoviDinosauro:
				i = mess.indexOf("=");
				j = mess.indexOf(",", i+1);
				tok = Integer.parseInt(mess.substring(i+1, j));
				i = mess.indexOf("=", j+1);
				j = mess.indexOf(",", i+1);
				pass = mess.substring(i+1, j);
				i = mess.indexOf("{", j+1);
				j = mess.indexOf(",", i+1);
				k = Integer.parseInt(mess.substring(i+1, j));
				i = mess.indexOf("}", j);
				l = Integer.parseInt(mess.substring(j+1, i));
				control = partita.muoviDinosauro(tok, pass, k, l);
				if ( (Boolean) control.get(0) == false ){
					if( Integer.parseInt(String.valueOf(control.get(1))) != 101){
						result = errore(Integer.parseInt(String.valueOf(control.get(1))));
						sender(result);
					}
				}
				else{
					result = "@ok";
					if ( (Boolean) control.get(1) ){
						result = result + ",@combattimento,";
						if ( String.valueOf(control.get(2)).equals("v") )
							result = result + "v";
						else result = result + "p";
					}
					sender(result);
				}
				break;
				
			case cresciDinosauro:
				i = mess.indexOf("=");
				j = mess.indexOf(",", i+1);
				tok = Integer.parseInt(mess.substring(i+1, j));
				i = mess.indexOf("=", j+1);
				pass = mess.substring(i+1);
				control = partita.cresciDinosauro(tok, pass);
				if ( (Boolean) control.get(0) == false ){
					if( Integer.parseInt(String.valueOf(control.get(1))) != 101){
						result = errore(Integer.parseInt(String.valueOf(control.get(1))));
						sender(result);
					}
				}
				else{
					result = "@ok";
					sender(result);
				}
				break;
				
			case deponiUovo:
				i = mess.indexOf("=");
				j = mess.indexOf(",", i+1);
				tok = Integer.parseInt(mess.substring(i+1, j));
				i = mess.indexOf("=", j+1);
				pass = mess.substring(i+1);
				control = partita.deponiUovo(tok, pass);
				if ( (Boolean) control.get(0) == false ){
					if( Integer.parseInt(String.valueOf(control.get(1))) != 101){
						result = errore(Integer.parseInt(String.valueOf(control.get(1))));
						sender(result);
					}
				}
				else{
					result = "@ok," + String.valueOf(control.get(1));
					sender(result);
				}
				break;
				
			case confermaTurno:
				i = mess.indexOf("=");
				tok = Integer.parseInt(mess.substring(i+1));
				control = partita.confermaTurno(tok);
				if ( (Boolean) control.get(0) == false )
					result = errore(Integer.parseInt(String.valueOf(control.get(1))));
				else{
					result = "@ok";
					}
				sender(result);
				break;
				
			case passaTurno:
				i = mess.indexOf("=");
				tok = Integer.parseInt(mess.substring(i+1));
				control = partita.passaTurno(tok);
				if ( (Boolean) control.get(0) == false ){
					if( Integer.parseInt(String.valueOf(control.get(1))) != 101 ){
					result = errore(Integer.parseInt(String.valueOf(control.get(1))));
					sender(result);
					}
				}	
				else{
					result = "@ok";
					sender(result);
					partita.cambioTurno();
					}
				break;
		}
	}
	
	/**
	 * Metodo usato per inviare il messaggio al client.
	 * @param mess Messaggio ricevuto e creato in precedenza.
	 */
	private void sender(String mess){
		
		System.out.println("Invio messaggio: (" + mess + ")");
		try {
			buffout.write(mess);
			buffout.newLine();
			buffout.flush();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * Metodo chiamato nel caso venga fornito un errore dalla logica del server. In base al numero ricevuto, crea ed invia la stringa corrispondente.
	 * @param err Numero indicante l'errore riscontrato dalla logica del server.
	 * @return Una stringa da mandare al client indicante l'errore riscontrato.
	 * @throws IOException Lanciata nel caso di errori di Input/Output.
	 */
	private String errore(int err) throws IOException{
		String mess = null;
		
		switch(err){
		case 0:
			mess = "@no,@usernameOccupato";
			break;
		case 1:
			mess = "@no,@autenticazioneFallita"; 
			break;
		case 2:
			mess = "@no,@nomeRazzaOccupato";
			break;
		case 3:
			mess = "@no,@tokenNonValido";
			break;
		case 4:
			mess = "@no,@troppiGiocatori";
			break;
		case 5:
			mess = "@no,@nonInPartita";
			break;
		case 6:
			mess = "@no,@idNonValido";
			break;
		case 7:
			mess = "@no,@destinazioneNonValida";
			break;
		case 8:
			mess = "@no,@raggiuntoLimiteMosseDinosauro";
			break;
		case 9:
			mess = "@no,@mortePerInedia";
			break;
		case 10:
			mess = "@no,@nonIlTuoTurno";
			break;
		case 11:
			mess = "@no,@raggiuntoNumeroMaxDinosauri";
			break;
		case 95:
			mess = "@no,@nessunoInPartita";
			break;
		case 96:
			mess = "@no,@razzaNonCreata";
			break;
		case 97:
			mess = "@no,@utenteHaRazza";
			break;
		case 98:
			mess = "@no,@nomeRazzaNonValido";
			break;
		case 99:
			mess = "@no,@userPassNonValidi";
			break;
		}
		return mess;
	}
	
	/**
	 * Metodo chiamato per creare la stinga da mandare al client contente il nome del giocatore di cui e' il turno.
	 * @param username Nome del giocatore di cui e' il turno corrente.
	 */
	public void cambioTurno(String username){
		String mess;
		
		mess = "@cambioTurno," + username;
		sender(mess);
	}
	
	/**
	 * Metodo chiamato per creare la stringa contenente il nome del giocatore a cui e' morta la specie.
	 * @param username Nome del giocatore a cui e' morta la specie.
	 */
	public void specieMorta(String username){
		String mess;
		
		mess = "@specieMorta," + username;
		sender(mess);
	}
}